// Copyright (c) 2011 Zeex
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <string>
#include <vector>

#include "amxdbgw.h"
#include "amxstacktrace.h"
#include "fileutils.h"
#include "logprintf.h"

#include "amx/amx.h"
#include "amx/amxdbg.h"

static cell GetCallTarget(AMX *amx, cell callAddr) {
    auto hdr = reinterpret_cast<AMX_HEADER*>(amx->base);
    auto code = reinterpret_cast<cell>(amx->base) + hdr->cod;
    // Get address of the function being called
    return *reinterpret_cast<cell*>(callAddr + sizeof(cell) + code) - code;
}

std::vector<AmxFunctionCall> GetAmxStackTrace(AMX *amx) {
    std::vector<AmxFunctionCall> calls;

    auto hdr = reinterpret_cast<AMX_HEADER*>(amx->base);
    auto data = reinterpret_cast<cell>(amx->base + hdr->dat);

    for (auto frame = amx->frm; frame > amx->stk;) {
        // Get address of the CALL XXXX instruction 
        cell callAddr = *(reinterpret_cast<cell*>(data + frame) + 1) - 2*sizeof(cell);

        AmxFunctionCall call = {frame, callAddr, GetCallTarget(amx, callAddr)};
        calls.push_back(call);

        // Go to the previous frame
        frame = *reinterpret_cast<cell*>(data + frame);
    } 

    return calls;
}

static int GetNumberOfArguments(AMX *amx, cell frame) {
    auto *hdr = reinterpret_cast<AMX_HEADER*>(amx->base);
    auto *data = amx->data ? amx->data : amx->base + hdr->dat;

    /* the number of bytes is on the stack, at "frm + 2*cell" */
    cell bytes = *reinterpret_cast<cell*>(data + frame + 2*sizeof(cell));

    /* the number of arguments is the number of bytes divided
    * by the size of a cell */
    return bytes / sizeof(cell);
}

static int PrintAmxStackTraceInternal(AMX *amx, AMX_DBG *amxdbg) {
    auto calls = GetAmxStackTrace(amx);
    auto depth = calls.size();
    if (depth > 1) {
        for (auto i = 0; i < calls.size(); i++) {
            auto level = depth - (i + 1);
            if (calls[i].callAddress > 0) {   
                logprintf("#%d: %s at line %ld in %s", level, 
                    LookupFunction(amxdbg, calls[i].functionAddress), 
                    LookupLine(amxdbg, calls[i].callAddress), 
                    fileutils::GetBaseName(LookupFile(amxdbg, calls[i].callAddress)).c_str()); 
            }
        }
        logprintf("#0: %s (entry point) in %s", 
            LookupFunction(amxdbg, calls[depth - 2].callAddress),
            fileutils::GetBaseName(LookupFile(amxdbg, calls[depth - 2].callAddress)).c_str());
    } else if (depth != 0) {
        logprintf("#0: %s (entry point) in %s", LookupFunction(amxdbg, amx->cip), 
            fileutils::GetBaseName(LookupFile(amxdbg, amx->cip)).c_str());
    }
    return depth;
}

void PrintAmxStackTrace(AMX *amx, AMX_DBG *amxdbg) {
    logprintf("Stack trace (most recent call first):");   
    if (PrintAmxStackTraceInternal(amx, amxdbg) == 0) {
        logprintf("Invalid AMX state (stack corrupted?)");
    }
}

void PrintAmxStackTrace(AMX *amx, AMX_DBG *amxdbg, const std::string &amxname) {
    logprintf("Stack trace of %s (most recent call first):", amxname.c_str());   
    if (PrintAmxStackTraceInternal(amx, amxdbg) == 0) {
        logprintf("Invalid AMX state (stack corrupted?)");
    }
}
